﻿
import com.kirillam.handy.core.HATControlClip;
import com.kirillam.handy.datastructures.Set;
import com.kirillam.handy.events.HATEvent;
import com.kirillam.handy.repeaters.HATJob;
import com.kirillam.handy.util.ObjectUtil;



/**
* Basic Handy object.
* 
* @author Kirill Mourzenko
*/

class com.kirillam.handy.core.HATObject extends MovieClip
{
	/* Unique id. */
	
	public	var hatId			: Function;
	
	
	/* Logging */
	
	public	var INFO			: Function;
	public	var DEBUG			: Function;
	public	var WARN			: Function;
	public	var ERROR			: Function;
	public	var SEVERE			: Function;
	
	
	
	/* Event listeners */
	
	private var events			: Object;
	
	
	/* Table of scheduled jobs. */
	
	private var jobs			: Object;
	
	
	
	/** Constructor */
	
	public	function HATObject() 
	{
		super();
		
		
		if ( Object.prototype.hatId == null ) {
			
			// GENERATE UNIQUE IDENTIFIERS FOR ANY OBJECT AT THE TIME THAT THE IDENTIFIER IS FIRST REFERENCED ON AN OBJECT.
		
			Object.prototype.__hatId__	= 0;
			_global.ASSetPropFlags( Object.prototype, "__hatId__", 3, true );
				
			Object.prototype.hatId		= function() : Number
			{
				var HATID	: Number	= Object.prototype.__hatId__++;
				this.hatId				= function()
				{
					return HATID;
				}
				
				_global.ASSetPropFlags( this, "hatId", 7, true );
				
				return HATID;
			}
			
			_global.ASSetPropFlags( Object.prototype, "hatId", 7, true );
			
			
			// CREATE THE CONTROL CLIP INSTANCE
			
			HATControlClip.instance;
		}
		
		events							= new Object();
		jobs							= new Object();
	}
	
	
	// EVENT HANDLING	--	FOR MANAGING LISTENERS AND SENDING EVENTS TO THEM.
	
	
	/** 
	 * Adds a handler for an event sent by this object.
	 * 
	 * @param	event		Event identifier/name.
	 * @param	target		Object to call the handler method on.
	 * @param	handler		Handler method that'll get called when the event is sent.
	 * @param	...			Any number of arguments to pass through to the handler when it'll get called.
	 */
 
	public function handle( event: String, target : Object, handler : Function, useStrongReference : Boolean )
	{
		if ( events[ event ] == null ) {
			events[ event ]			= new Set();
		}
		
		// STORE THE FUNCTION REFERENCE IN THE OBJECT SO IT DOESN'T GET DELETED IN CASE IT'S NAMELESS.
		
		if( target.__HAT_HANDLERS == null ) {
			target.__HAT_HANDLERS	= new Object();
			_global.ASSetPropFlags( target, "__HAT_HANDLERS", 7, true );
		}
		
		var eventId		: String	= hatId() + "." + event;
		if( target.__HAT_HANDLERS[ eventId ] == null ) {
			target.__HAT_HANDLERS[ eventId ] = arguments.slice( 2 );
		}
		
		events[ event ].add( target, !useStrongReference );
	}
	
	
	/**
	 * Removes a handler for an event.
	 * 
	 * @param	event		Event identifier/name.
	 * @param	target		Object that the handler method was supposed to be called on.
	 */
	
	public function ignore( event: String, target : Object )
	{
		var eventId		: String	= hatId() + "." + event;
		delete target.__HAT_HANDLERS[ eventId ];
		
		
		events[ event ].remove( target );
		
		if ( events[ event ].size <= 0 ) {
			delete events[ event ];
		}
	}
	
	
	/**
	 * Removes all handler for an event.
	 * 
	 * @param	target		Object that the handler methods were supposed to be called on.
	 */
	
	public function ignoreAll( target : Object )
	{
		for ( var e : String in events ) {
			
			if ( events[ e ].isIn( target ) ) {
				ignore( e, target );
			}
		}
	}
	
	
	/**
	 * Tells if a method is set as a handler for an event sent by this object.
	 * 
	 * @param	event		Event identifier/name.
	 * @param	target		Object to call the handler method on.
	 * @param	handler		Handler method that'll get called when the event is sent.
	 * 
	 * @return true if the specified event has the target object as the handler, false if not.
	 */
	
	public function hasHandler( event: String, target : Object ) : Boolean
	{
		return events[ event ].isIn( target );
	}
	
	
	/**
	 * Tells if the event has any handlers handing it.
	 * 
	 * @param	event	Event identifier.
	 * @return	true if the event has handlers handling it, false if not.
	 */
	
	public function isHandled( event : String ) : Boolean
	{
		return events[ event ] != null;
	}
	
	
	/**
	 * Sends out the event to all handlers if any for that event.
	 * 
	 * @param	event		An event object describing the event.
	 */
	
	private function sendEvent( event : HATEvent )
	{
		event.target					= this;

		var handlers		: Set		= events[ event.name ];
		if ( handlers != null && handlers.size > 0 ) {
			
			// COPY EXISTING HANDLERS SO THAT THEY DON'T GET MODIFIED AS RESULT OF EXECUTING A HANDLER.
			
			var hc			: Array		= new Array();
			handlers.forEach( this, function( o : Object )
			{
				hc.push( o );
			} );
			
			// EXECUTE THE HANDLERS.
			
			var eventId		: String	= hatId() + "." + event.name;
			for ( var i : Number = 0; i < hc.length; i++ ) {
				
				var target	: Object	= hc[ i ];
				var handler	: Array		= target.__HAT_HANDLERS[ eventId ];
				handler[ 0 ].apply( target, [ event ].concat( handler.slice( 2 ) ) );
			}
		} else {
			delete events[ event.name ];
		}
	}
	
	
	
	// JOB MANAGEMENT	--	METHODS TO HELP WITH STORING JOBS WITHOUT HAVING TO DECLARE VARIABLES FOR EACH OF THEM.
	
	/**
	 * Stores the job in the object.
	 * 
	 * Usage:
	 * 		
	 * 		saveJob( "jobId", job )		To store the job with the given name.
	 * 		saveJob( job )				To store the job annonymously.
	 */
	
	private function saveJob()
	{
		var id 	: Object;
		var job : HATJob;
		
		id					= arguments[ 0 ];
			
		if ( ObjectUtil.isString( arguments[ 0 ] ) ) {
			job				= HATJob( arguments[ 1 ] );
		} else {
			job				= HATJob( arguments[ 0 ] );
		}
		
		if ( jobs[ id ] != null ) {
			jobs[ id ].quit();
		}
		
		jobs[ id ]			= job;
	}
	
	
	/**
	 * Removes the job from the object.
	 * 
	 * @param	id	Either the id used to store the job or the job itself if it was stored annonymously.
	 */
	
	private function removeJob( id : Object )
	{
		jobs[ id ].quit();
		delete jobs[ id ];
	}
	
	
	/**
	 * Retrieves the job from the object.
	 * 
	 * @param	id	Either the id used to store the job or the job itself if it was stored annonymously.
	 */
	
	private function getJob( id : Object ) : HATJob
	{
		return jobs[ id ];
	}
	
	
	/**
	 * Tells whether the job is running.
	 * 
	 * @param	id	Either the id used to store the job or the job itself if it was stored annonymously.
	 * @return	true if the job is running, false otherwise.
	 */
	
	private function isJobActive( id : Object ) : Boolean
	{
		return Boolean( jobs[ id ].isActive() );
	}
}